package com.optimize.project.netCache;

import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * 自定义DiskCacheManager类
 * <h3>Description</h3>
 * TODO
 * <h3>Author</h3> luzhenbang
 * <h3>Date</h3> 2018/4/17 09:44
 * <h3>Copyright</h3> Copyright (c)2018 Shenzhen TL  Co., Ltd. Inc. All rights reserved.
 */
public class DiskCacheManager {

    private static DiskCacheManager instance = null;
    private Context  context;
    private static final String TAG = DiskCacheManager.class.getName() + "=======>";
    /** 缓存文件名*/
    private static final String CACHE_FILE_NAME = "cache";
    /** 缓存文件大小 1MB */
    private static final int MAX_SIZE = 1*1024*1024;
    /** 表示单个节点对应的数据个数，也就是同一个key可以对应多少个缓存文件，一般来说我们都选取1. */
    private static final int VALUE_COUNT = 1;
    /** 表示应用的版本号 当appVersion改变时，之前的缓存都会被清除，所以如非必要，我们为其指定一个1，不再改变即可*/
    private static  int APP_VERSION = 1;
    private  DiskLruCache diskLruCache = null;

    private DiskCacheManager(Context applicationContext){
        //为了防止内存泄漏不要使用 Activity上下转为 Application 上下文
        if (applicationContext instanceof Activity ||
                applicationContext instanceof AppCompatActivity){
            this.context = applicationContext.getApplicationContext();
        }else {
            this.context  = applicationContext;
        }
    }

    /**
     * 单例模式
     * @param applicationContext Application 上下文
     * @return
     */
    public static DiskCacheManager getInstance(Context applicationContext) {
        if (null == instance){
            synchronized (DiskCacheManager.class){
                if (null == instance){
                    instance = new DiskCacheManager(applicationContext);
                }
            }
        }
        return instance;
    }

    /**
     * 打开缓存
     * @return
     */
    public DiskLruCache open(){
        if (diskLruCache == null || diskLruCache.isClosed()) {
            try {
                diskLruCache = DiskLruCache.open(getCache(),APP_VERSION,VALUE_COUNT,MAX_SIZE);
            } catch (IOException e) {
                Log.e(TAG,e.getMessage());
                Log.i(TAG,e.getMessage());
            }
        }
        return diskLruCache;
    }


    public DiskLruCache open(int appVersion, int valueCount){
        if (diskLruCache == null || diskLruCache.isClosed()) {
            try {
                diskLruCache = DiskLruCache.open(getCache(),appVersion,valueCount,MAX_SIZE);
            } catch (IOException e) {
                Log.e(TAG,e.getMessage());
                Log.i(TAG,e.getMessage());
            }
        }
        return diskLruCache;
    }



    /**
     * 通过key可以获得一个DiskLruCache.Editor
     * 通过Editor可以得到一个输出流，进而缓存到本地存储上
     * @param key
     * @return
     */
    public DiskLruCache.Editor editor(String key){
        if (null != diskLruCache){

            try {
                key = hashKeyForDisk(key);
                return diskLruCache.edit(key);
            } catch (IOException e) {
                Log.e(TAG,e.getMessage());
                Log.i(TAG,e.getMessage());
                return null;
            }
        }
        return null;
    }


    /**
     * 获取输出流
     * @param editor
     * @param index
     * @return
     */
    public OutputStream newOS(DiskLruCache.Editor editor, int index){
        OutputStream os = null;
        if (null != editor){
            try {
                os = editor.newOutputStream(index);
            } catch (IOException e) {
                Log.e(TAG,e.getMessage());
                Log.i(TAG,e.getMessage());
            }
        }
        return  os;
    }


    /**
     * 获取输入流
     * @param editor 该参数 不能与{{@link #newOS(DiskLruCache.Editor, int)}}的editor
     *               是同一个实例对象，否则会报异常 IllegalStateException
     *               所以获取输入流最好使用{{@link #getIS(DiskLruCache.Snapshot, int)}}
     * @param index
     * @return 返回最后提交的内容 没有返回null
     */
    public InputStream newIS(DiskLruCache.Editor editor, int index){
        InputStream is = null;
        if (null != editor){
            try {
                is = editor.newInputStream(index);
            } catch (IOException e) {
                Log.e(TAG,e.getMessage());
                Log.i(TAG,e.getMessage());
            }
        }
        return  is;
    }


    /**
     * 保存字符串到缓存文件中
     * @param editor
     * @param index
     * @param value
     */
    public void setString(DiskLruCache.Editor editor,int index , String value){
        if (null != editor){
            try {
                editor.set(index,value);
            } catch (IOException e) {
                Log.e(TAG,e.getMessage());
                Log.i(TAG,e.getMessage());
            }
        }
    }


    /**
     * 获取缓存字符串
     * @param editor 该参数 不能与{{@link #newOS(DiskLruCache.Editor, int)}}的editor
     *               是同一个实例对象，否则会报异常 IllegalStateException
     *               所以获取字符串最好使用{{@link #getString(DiskLruCache.Snapshot, int)}}
     * @param index
     * @return
     */
    public String getString(DiskLruCache.Editor editor,int index ){
        if (null != editor){
            try {
              return editor.getString(index);
            } catch (IOException e) {
                Log.e(TAG,e.getMessage());
                Log.i(TAG,e.getMessage());
                return null;
            }
        }
        return null;
    }


    /**
     * 通过 key 获取快照   内容必须提交commit后才可以，否则会返回null
     * @param key 未加密的key
     * @return
     */
    public DiskLruCache.Snapshot snapshot(String key){
        DiskLruCache.Snapshot  snapshot = null;
        if (null != diskLruCache){
            try {
                key = hashKeyForDisk(key);
                snapshot =  diskLruCache.get(key);
            } catch (IOException e) {
                Log.e(TAG,e.getMessage());
                Log.i(TAG,e.getMessage());
                snapshot = null;
            }
        }

        return snapshot;
    }


    /**
     * 获取输入流
     * @param snapshot
     * @param index
     * @return
     */
    public InputStream getIS(DiskLruCache.Snapshot snapshot,int index){
        InputStream is = null;
        if (null != snapshot){
            is = snapshot.getInputStream(index);
        }
        return is;
    }


    /**
     * 获取字符串
     * @param snapshot
     * @param index
     * @return
     */
    public String getString(DiskLruCache.Snapshot snapshot,int index){
        String is = null;
        if (null != snapshot){
            try {
                is = snapshot.getString(index);
            } catch (IOException e) {
                Log.e(TAG,e.getMessage());
                Log.i(TAG,e.getMessage());
                is = "";
            }
        }
        return is;
    }



    /**
     * 获取
     * @param key
     * @return
     */
    public Bitmap getBitmap(String key){
        DiskLruCache.Snapshot snapshot = snapshot(key);
        if(snapshot != null){
            Bitmap bitmap = BitmapFactory.decodeStream(snapshot.getInputStream(0));
            return bitmap;
        }
        return null;
    }


    /**
     * 获取
     * @param editor 该参数 不能与{{@link #newOS(DiskLruCache.Editor, int)}}的editor
     *               是同一个实例对象，否则会报异常 IllegalStateException 所以获取输入流最好使用{{@link #getBitmap(String)}}
     * @return
     */
    public Bitmap getBitmap(DiskLruCache.Editor editor){
        if(editor != null){
            InputStream is = newIS(editor,0);
            if (null != is){
                Bitmap bitmap = BitmapFactory.decodeStream(is);
                return bitmap;
            }
        }
        return null;
    }



    /**
     * 图片下载发生异常时，可以通过这个方法回退整个操作
     */
    public void abort(DiskLruCache.Editor editor){
        if (null != editor){
            try {
                editor.abort();
            } catch (IOException e) {
                Log.e(TAG,e.getMessage());
                Log.i(TAG,e.getMessage());
            }
        }
    }


    /**
     * 提交写入操作，这个放在在写入缓存数据时是一定要调用的
     * @param editor
     */
    public void commit(DiskLruCache.Editor editor){
        if (null != editor){
            try {
                editor.commit();
            } catch (IOException e) {
                Log.e(TAG,e.getMessage());
                Log.i(TAG,e.getMessage());
            }
        }
    }

    /**
     * 生成MD5暗文
     * @param key
     * @return
     */
    public static String md5Encrypt(String key) {// 保持编码为UTF-8
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(key.getBytes());
            byte b[] = md.digest();
            int i;
            StringBuffer buf = new StringBuffer("");
            for (int offset = 0; offset < b.length; offset++) {
                i = b[offset];
                if (i < 0)
                    i += 256;
                if (i < 16)
                    buf.append("0");
                buf.append(Integer.toHexString(i));
            }
            return buf.toString().toUpperCase();
        } catch (NoSuchAlgorithmException e) {
            Log.e(TAG,e.getMessage());
            Log.i(TAG,e.getMessage());
            return key;
        }
    }


    /**
     * A hashing method that changes a string (like a URL) into a hash suitable for using as a
     * disk filename.
     */
    public static String hashKeyForDisk(String key) {
        String cacheKey;
        try {
            final MessageDigest mDigest = MessageDigest.getInstance("MD5");
            mDigest.update(key.getBytes());
            cacheKey = bytesToHexString(mDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            cacheKey = String.valueOf(key.hashCode());
        }
        return cacheKey;
    }

    private static String bytesToHexString(byte[] bytes) {
        // http://stackoverflow.com/questions/332079
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(0xFF & bytes[i]);
            if (hex.length() == 1) {
                sb.append('0');
            }
            sb.append(hex);
        }
        return sb.toString();
    }


    /**
     * 获取当前应用版本号
     */
    public void getCode(){
        PackageManager packageInfo  = context.getPackageManager();
//        String packName = context.getPackageName();
        try {
            PackageInfo info = packageInfo.getPackageInfo(context.getPackageName(),0);
            APP_VERSION = info.versionCode;
        } catch (PackageManager.NameNotFoundException e) {
//            e.printStackTrace();
            Log.e(TAG,e.getMessage());
            Log.i(TAG,e.getMessage());
            APP_VERSION = 1;
        }
    }

    /**
     * 根据key值来删除对应的数据，如果该数据正在被编辑，则不能删除
     * @param key
     */
    public boolean remove(String key){
        if (null != diskLruCache){
            try {
               return diskLruCache.remove(key);
            } catch (IOException e) {
                Log.e(TAG,e.getMessage());
                Log.i(TAG,e.getMessage());
                return false;
            }
        }
        return false;
    }


    /**
     *
     * 删除所有的缓存数据
     *
     */
    public void delete(){
        if (null != diskLruCache){
            try {
                diskLruCache.delete();
            } catch (IOException e) {
                Log.e(TAG,e.getMessage());
                Log.i(TAG,e.getMessage());
            }
        }
    }


    /**
     * 这个方法用于将内存中的操作记录同步到日志文件
     * （也就是journal文件，系统LRU算法依赖于这个文件，因为这个文件中保存着对数据的操作记录）
     * 当中；但其实并不是每次写入缓存都要调用一次flush()方法的，频繁地调用并不会带来任何好处，
     * 只会额外增加同步journal文件的时间。比较标准的做法就是在Activity的onPause()方法中去调用一次flush()方法就可以了。
     */
    public void flush(){
        if (null != diskLruCache){
            try {
                diskLruCache.flush();
            } catch (IOException e) {
                Log.e(TAG,e.getMessage());
                Log.i(TAG,e.getMessage());
            }
        }
    }


    /**
     * 这个方法用于将DiskLruCache关闭掉，是和open()方法对应的一个方法。
     * 关闭掉了之后就不能再调用DiskLruCache中任何操作缓存数据的方法，
     * 通常只应该在Activity的onDestroy()方法中去调用close()方法
     */
    public void close(){
        if (null != diskLruCache && !diskLruCache.isClosed()){
            try {
                diskLruCache.close();
            } catch (IOException e) {
                Log.e(TAG,e.getMessage());
                Log.i(TAG,e.getMessage());
            }
        }
    }


    /**
     * 返回当前缓存路径下所有缓存数据的大小，以byte为单位
     * @return
     */
    public long size(){
        if (null != diskLruCache){
           return diskLruCache.size();
        }
        return 0;
    }


    /**
     * 获取缓存文件
     *
     * context.getExternalCacheDir().getPath()
     * /storage/emulated/0/Android/data/package_name/cache 这个外部存储目录中
     * @return
     */
    private File getCache(){
        File file ;
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
            //  /storage/emulated/0/com.optimize.project/cache
            StringBuffer buffer = new StringBuffer();
            buffer.append(Environment.getExternalStorageDirectory().getAbsolutePath())
                    .append(File.separator)
                    .append(context.getPackageName())
                    .append(File.separator)
                    .append(CACHE_FILE_NAME);
            file = new File(buffer.toString());
        }else {
            ///data/data/package_name/cache
            file = new File(context.getCacheDir(),CACHE_FILE_NAME);
        }

        if (!file.exists()){
            file.mkdirs();
        }
        return file;
    }


}
